/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Haystack Software Inc. All rights reserved.
 *  Licensed under the PolyForm Strict License 1.0.0. See License.txt in the project root for
 *  license information.
 *--------------------------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See code-license.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import "vs/css!./media/sidebarpart"
import "vs/workbench/browser/parts/sidebar/sidebarActions"
import {
  ActivityBarPosition,
  IWorkbenchLayoutService,
  LayoutSettings,
  Parts,
  Position as SideBarPosition,
} from "vs/workbench/services/layout/browser/layoutService"
import {
  SidebarFocusContext,
  ActiveViewletContext,
} from "vs/workbench/common/contextkeys"
import {
  IStorageService,
  StorageScope,
  StorageTarget,
} from "vs/platform/storage/common/storage"
import { IContextMenuService } from "vs/platform/contextview/browser/contextView"
import { IKeybindingService } from "vs/platform/keybinding/common/keybinding"
import { IInstantiationService } from "vs/platform/instantiation/common/instantiation"
import { IThemeService } from "vs/platform/theme/common/themeService"
import { contrastBorder } from "vs/platform/theme/common/colorRegistry"
import {
  SIDE_BAR_TITLE_FOREGROUND,
  SIDE_BAR_BACKGROUND,
  SIDE_BAR_FOREGROUND,
  SIDE_BAR_BORDER,
  SIDE_BAR_DRAG_AND_DROP_BACKGROUND,
  ACTIVITY_BAR_BADGE_BACKGROUND,
  ACTIVITY_BAR_BADGE_FOREGROUND,
  ACTIVITY_BAR_TOP_FOREGROUND,
  ACTIVITY_BAR_TOP_ACTIVE_BORDER,
  ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND,
  ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER,
} from "vs/workbench/common/theme"
import { INotificationService } from "vs/platform/notification/common/notification"
import { IContextKeyService } from "vs/platform/contextkey/common/contextkey"
import { AnchorAlignment } from "vs/base/browser/ui/contextview/contextview"
import { IExtensionService } from "vs/workbench/services/extensions/common/extensions"
import { LayoutPriority } from "vs/base/browser/ui/grid/grid"
import { assertIsDefined } from "vs/base/common/types"
import { IViewDescriptorService } from "vs/workbench/common/views"
import {
  AbstractPaneCompositePart,
  CompositeBarPosition,
} from "vs/workbench/browser/parts/paneCompositePart"
import {
  ActivityBarCompositeBar,
  ActivitybarPart,
} from "vs/workbench/browser/parts/activitybar/activitybarPart"
import { ActionsOrientation } from "vs/base/browser/ui/actionbar/actionbar"
import { HoverPosition } from "vs/base/browser/ui/hover/hoverWidget"
import { IPaneCompositeBarOptions } from "vs/workbench/browser/parts/paneCompositeBar"
import { IConfigurationService } from "vs/platform/configuration/common/configuration"
import {
  Action2,
  IMenuService,
  registerAction2,
} from "vs/platform/actions/common/actions"
import { Separator } from "vs/base/common/actions"
import { ToggleActivityBarVisibilityActionId } from "vs/workbench/browser/actions/layoutActions"
import { localize2 } from "vs/nls"
import { IHoverService } from "vs/platform/hover/browser/hover"

export class SidebarPart extends AbstractPaneCompositePart {
  static readonly activeViewletSettingsKey = "workbench.sidebar.activeviewletid"

  //#region IView

  readonly minimumWidth: number = 170
  readonly maximumWidth: number = Number.POSITIVE_INFINITY
  readonly minimumHeight: number = 0
  readonly maximumHeight: number = Number.POSITIVE_INFINITY
  override get snap(): boolean {
    return true
  }

  readonly priority: LayoutPriority = LayoutPriority.Low

  get preferredWidth(): number | undefined {
    const viewlet = this.getActivePaneComposite()

    if (!viewlet) {
      return
    }

    const width = viewlet.getOptimalWidth()
    if (typeof width !== "number") {
      return
    }

    return Math.max(width, 300)
  }

  private readonly activityBarPart = this._register(
    this.instantiationService.createInstance(ActivitybarPart, this)
  )

  //#endregion

  constructor(
    @INotificationService notificationService: INotificationService,
    @IStorageService storageService: IStorageService,
    @IContextMenuService contextMenuService: IContextMenuService,
    @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
    @IKeybindingService keybindingService: IKeybindingService,
    @IHoverService hoverService: IHoverService,
    @IInstantiationService instantiationService: IInstantiationService,
    @IThemeService themeService: IThemeService,
    @IViewDescriptorService viewDescriptorService: IViewDescriptorService,
    @IContextKeyService contextKeyService: IContextKeyService,
    @IExtensionService extensionService: IExtensionService,
    @IConfigurationService
    private readonly configurationService: IConfigurationService,
    @IMenuService menuService: IMenuService
  ) {
    super(
      Parts.SIDEBAR_PART,
      {
        hasTitle: true,
        borderWidth: () =>
          this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)
            ? 1
            : 0,
      },
      SidebarPart.activeViewletSettingsKey,
      ActiveViewletContext.bindTo(contextKeyService),
      SidebarFocusContext.bindTo(contextKeyService),
      "sideBar",
      "viewlet",
      SIDE_BAR_TITLE_FOREGROUND,
      notificationService,
      storageService,
      contextMenuService,
      layoutService,
      keybindingService,
      hoverService,
      instantiationService,
      themeService,
      viewDescriptorService,
      contextKeyService,
      extensionService,
      menuService
    )

    this.rememberActivityBarVisiblePosition()
    this._register(
      configurationService.onDidChangeConfiguration((e) => {
        if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) {
          this.onDidChangeActivityBarLocation()
        }
      })
    )

    this.registerActions()
  }

  private onDidChangeActivityBarLocation(): void {
    this.activityBarPart.hide()

    this.updateCompositeBar()

    const id = this.getActiveComposite()?.getId()
    if (id) {
      this.onTitleAreaUpdate(id)
    }

    if (this.shouldShowActivityBar()) {
      this.activityBarPart.show()
    }

    this.rememberActivityBarVisiblePosition()
  }

  override updateStyles(): void {
    super.updateStyles()

    const container = assertIsDefined(this.getContainer())

    container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || ""
    container.style.color = this.getColor(SIDE_BAR_FOREGROUND) || ""

    const borderColor =
      this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)
    const isPositionLeft =
      this.layoutService.getSideBarPosition() === SideBarPosition.LEFT
    container.style.borderRightWidth =
      borderColor && isPositionLeft ? "1px" : ""
    container.style.borderRightStyle =
      borderColor && isPositionLeft ? "solid" : ""
    container.style.borderRightColor = isPositionLeft ? borderColor || "" : ""
    container.style.borderLeftWidth =
      borderColor && !isPositionLeft ? "1px" : ""
    container.style.borderLeftStyle =
      borderColor && !isPositionLeft ? "solid" : ""
    container.style.borderLeftColor = !isPositionLeft ? borderColor || "" : ""
    container.style.outlineColor =
      this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? ""
  }

  override shouldSerialize(): boolean {
    return true
  }

  override layout(
    width: number,
    height: number,
    top: number,
    left: number
  ): void {
    if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) {
      return
    }

    super.layout(width, height, top, left)
  }

  protected override getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
    return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT
      ? AnchorAlignment.LEFT
      : AnchorAlignment.RIGHT
  }

  protected override createCompositeBar(): ActivityBarCompositeBar {
    return this.instantiationService.createInstance(
      ActivityBarCompositeBar,
      this.getCompositeBarOptions(),
      this.partId,
      this,
      false
    )
  }

  protected getCompositeBarOptions(): IPaneCompositeBarOptions {
    return {
      partContainerClass: "sidebar",
      pinnedViewContainersKey: ActivitybarPart.pinnedViewContainersKey,
      placeholderViewContainersKey:
        ActivitybarPart.placeholderViewContainersKey,
      viewContainersWorkspaceStateKey:
        ActivitybarPart.viewContainersWorkspaceStateKey,
      icon: true,
      orientation: ActionsOrientation.HORIZONTAL,
      recomputeSizes: true,
      activityHoverOptions: {
        position: () =>
          this.getCompositeBarPosition() === CompositeBarPosition.BOTTOM
            ? HoverPosition.ABOVE
            : HoverPosition.BELOW,
      },
      fillExtraContextMenuActions: (actions) => {
        const viewsSubmenuAction = this.getViewsSubmenuAction()
        if (viewsSubmenuAction) {
          actions.push(new Separator())
          actions.push(viewsSubmenuAction)
        }
      },
      compositeSize: 0,
      iconSize: 16,
      overflowActionSize: 30,
      colors: (theme) => ({
        activeBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND),
        inactiveBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND),
        activeBorderBottomColor: theme.getColor(ACTIVITY_BAR_TOP_ACTIVE_BORDER),
        activeForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_FOREGROUND),
        inactiveForegroundColor: theme.getColor(
          ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND
        ),
        badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
        badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
        dragAndDropBorder: theme.getColor(
          ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER
        ),
      }),
      compact: true,
    }
  }

  protected shouldShowCompositeBar(): boolean {
    const activityBarPosition =
      this.configurationService.getValue<ActivityBarPosition>(
        LayoutSettings.ACTIVITY_BAR_LOCATION
      )
    return (
      activityBarPosition === ActivityBarPosition.TOP ||
      activityBarPosition === ActivityBarPosition.BOTTOM
    )
  }

  private shouldShowActivityBar(): boolean {
    if (this.shouldShowCompositeBar()) {
      return false
    }

    return (
      this.configurationService.getValue(
        LayoutSettings.ACTIVITY_BAR_LOCATION
      ) !== ActivityBarPosition.HIDDEN
    )
  }

  protected getCompositeBarPosition(): CompositeBarPosition {
    const activityBarPosition =
      this.configurationService.getValue<ActivityBarPosition>(
        LayoutSettings.ACTIVITY_BAR_LOCATION
      )
    switch (activityBarPosition) {
      case ActivityBarPosition.TOP:
        return CompositeBarPosition.TOP
      case ActivityBarPosition.BOTTOM:
        return CompositeBarPosition.BOTTOM
      case ActivityBarPosition.HIDDEN:
      case ActivityBarPosition.DEFAULT: // noop
      default:
        return CompositeBarPosition.TITLE
    }
  }

  private rememberActivityBarVisiblePosition(): void {
    const activityBarPosition = this.configurationService.getValue<string>(
      LayoutSettings.ACTIVITY_BAR_LOCATION
    )
    if (activityBarPosition !== ActivityBarPosition.HIDDEN) {
      this.storageService.store(
        LayoutSettings.ACTIVITY_BAR_LOCATION,
        activityBarPosition,
        StorageScope.PROFILE,
        StorageTarget.USER
      )
    }
  }

  private getRememberedActivityBarVisiblePosition(): ActivityBarPosition {
    const activityBarPosition = this.storageService.get(
      LayoutSettings.ACTIVITY_BAR_LOCATION,
      StorageScope.PROFILE
    )
    switch (activityBarPosition) {
      case ActivityBarPosition.TOP:
        return ActivityBarPosition.TOP
      case ActivityBarPosition.BOTTOM:
        return ActivityBarPosition.BOTTOM
      default:
        return ActivityBarPosition.DEFAULT
    }
  }

  override getPinnedPaneCompositeIds(): string[] {
    return this.shouldShowCompositeBar()
      ? super.getPinnedPaneCompositeIds()
      : this.activityBarPart.getPinnedPaneCompositeIds()
  }

  override getVisiblePaneCompositeIds(): string[] {
    return this.shouldShowCompositeBar()
      ? super.getVisiblePaneCompositeIds()
      : this.activityBarPart.getVisiblePaneCompositeIds()
  }

  async focusActivityBar(): Promise<void> {
    if (
      this.configurationService.getValue(
        LayoutSettings.ACTIVITY_BAR_LOCATION
      ) === ActivityBarPosition.HIDDEN
    ) {
      await this.configurationService.updateValue(
        LayoutSettings.ACTIVITY_BAR_LOCATION,
        this.getRememberedActivityBarVisiblePosition()
      )

      this.onDidChangeActivityBarLocation()
    }

    if (this.shouldShowCompositeBar()) {
      this.focusCompositeBar()
    } else {
      if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {
        this.layoutService.setPartHidden(false, Parts.ACTIVITYBAR_PART)
      }

      this.activityBarPart.show(true)
    }
  }

  private registerActions(): void {
    const that = this
    this._register(
      registerAction2(
        class extends Action2 {
          constructor() {
            super({
              id: ToggleActivityBarVisibilityActionId,
              title: localize2(
                "toggleActivityBar",
                "Toggle Activity Bar Visibility"
              ),
            })
          }
          run(): Promise<void> {
            const value =
              that.configurationService.getValue(
                LayoutSettings.ACTIVITY_BAR_LOCATION
              ) === ActivityBarPosition.HIDDEN
                ? that.getRememberedActivityBarVisiblePosition()
                : ActivityBarPosition.HIDDEN
            return that.configurationService.updateValue(
              LayoutSettings.ACTIVITY_BAR_LOCATION,
              value
            )
          }
        }
      )
    )
  }

  toJSON(): object {
    return {
      type: Parts.SIDEBAR_PART,
    }
  }
}
